package com.ruoyi.common.config.pay;

import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.ruoyi.common.enums.ServiceCodeEnum;
import com.ruoyi.common.enums.ServiceResponseCodeEnum;
import com.ruoyi.common.exception.BusinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 签名服务
 * Created by zouhl on 2018/3/1.
 */
@Component
public class SignService {
    private static final String DEFAULT_CHARSET = "UTF-8";
    private static final Logger logger = LoggerFactory.getLogger(SignService.class);


    /**
     * 从私钥证书中获取私钥
     * @param pfxContent 私钥证书Base64密文
     * @param password   私钥密码
     * @return 私钥
     * @throws Exception 解析异常
     */
    public static PrivateKey readPrivateKey(String pfxContent, String password, String keyStoreType) throws Exception {
        char[] pass = password.toCharArray();
            KeyStore ks = KeyStore.getInstance(keyStoreType);
            ByteArrayOutputStream os = new ByteArrayOutputStream(pfxContent.length());
            ks.load(new ByteArrayInputStream(Base64.getDecoder().decode(pfxContent)), pass);
            Enumeration<String> aliases = ks.aliases();
            String alias = aliases.nextElement();
            if (null == alias) {
                logger.error("解析证书失败，找不到证书别名");
                throw new Exception(ErrorCode.RUNTIME_ERROR + ":解析证书失败，找不到证书别名");
            }
            PrivateKey privateKey = (PrivateKey) ks.getKey(alias, pass);
            return privateKey;
    }

    /**
     * 签名
     *
     * @param plainText  签名原文
     * @param algorithm  签名算法
     * @param privateKey 私钥
     * @return 签名结果(BASE64)
     * @throws Exception 签名异常
     */
    public static String sign(String plainText, String algorithm, PrivateKey privateKey) throws Exception {
        Signature sig = Signature.getInstance(algorithm);
        sig.initSign(privateKey);
        sig.update(plainText.getBytes());
        byte[] b = sig.sign();
        return Base64.getEncoder().encodeToString(b);
    }


    /**
     *
     * @param requestMap 请求入参
     * @param serverCode 接口编码
     * @param merchId  商户号
     * @return
     * @throws Exception
     */
    public  HashMap<String, Object>  dealXibPost( Map<String,Object> requestMap,String serverCode,String merchId) throws Exception{
        //2.技术报文
        Date now = Calendar.getInstance().getTime();
        String format = "yyyyMMddHHmmss";
        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
        dateFormat.setLenient(false);
        String timestamp = dateFormat.format(now);
        HashMap<String, Object> reqMap = Maps.newHashMap();
        reqMap.put(CommonFieldName.SERIAL_N0, "sn" + System.currentTimeMillis());
        reqMap.put(CommonFieldName.TIMESTAMP, timestamp);
        //3.获取随机加密密钥
        String plainKey = AESUtils.getRandomKey();
        String encryptedKey;
        //XIB，银行的公钥内容
        String xibCerContent = PayAccountConfig.xibCerContent;
        //pem一样
        PublicKey xibPublicKey = RSAUtils.readPublicKey(xibCerContent,"cer");
        encryptedKey = RSAUtils.encryptByPublicKey(plainKey, xibPublicKey);

        //5.使用随机key加密业务数据
        String encryptedData = encryptDataMap(plainKey, requestMap);
        reqMap.put(CommonFieldName.ENCRYPTED_KEY, encryptedKey);
        reqMap.put(CommonFieldName.ENCRYPTED_DATA, encryptedData);
        reqMap.put(CommonFieldName.VERSION, "2.0");
        //6.计算签名
        String plainText = map2Plaintext(reqMap);
        System.out.println("签名原串:" + plainText);
        PrivateKey pk = getPriKey(PayAccountConfig.prikeyFilePath, PayAccountConfig.PASSWORD, PayAccountConfig.KEYSTORETYPE);
        String signature = RSAUtils.sign(plainText, RSAUtils.SIGNATURE_ALGORITHM, pk);
        reqMap.put(CommonFieldName.SIGNATURE, signature);
        logger.info("接口编码code={},发送报文request={}",serverCode,requestMap.toString());
        String url = PayAccountConfig.SERVER_URL +merchId+"/"+ serverCode;
        Map retMap = RequestClient.sendPost1(url, reqMap);
        logger.info("接口编码code={},响应报文={}",serverCode,retMap.toString());
        //6.响应验签
        String retSignature = (String) retMap.remove(CommonFieldName.SIGNATURE);
        String retPlainText = map2Plaintext(retMap);
        logger.info("签名原文={}",retPlainText);
        boolean verifySuccess = RSAUtils.verify(retPlainText.getBytes(), xibPublicKey, retSignature, RSAUtils.SIGNATURE_ALGORITHM);
        if ((verifySuccess)) {
            logger.info("接口编码code={},验签成功",serverCode);
        } else {
            throw new BusinessException("接口编码code="+serverCode+",验签失败");
        }
        //7.解密随机key
        String retEncryptedKey = (String) retMap.get(CommonFieldName.ENCRYPTED_KEY);
        byte[] decryptDataByte = AESUtils.decryptByPrivateKey(Encodes.decodeBase64(retEncryptedKey), Encodes.encodeBase64(pk.getEncoded()), RSAUtils.KEY_ALGORITHM);

        String notUTFString = new String(decryptDataByte, DEFAULT_CHARSET);
        String retPlainKey = new String(notUTFString.getBytes(DEFAULT_CHARSET));
        System.out.println("解密后的随机key：" + retPlainKey);

        //8.解密业务数据报文
        //使用随机key解密加密数据
        String retEncryptedData = (String) retMap.get(CommonFieldName.ENCRYPTED_DATA);
        String retBusiJson = AESUtils.decryptWithBC(retEncryptedData, retPlainKey);

        logger.info("接口编码code={},解密后的业务报文resultMap={}",serverCode,retBusiJson);
        //8.响应业务处理
        HashMap<String, Object> retBusiMap = RequestClient.fromJson(retBusiJson, HashMap.class);
        String respCode = (String) retBusiMap.get(CommonFieldName.RESP_CODE);
//        if (!"00000".equals(respCode)) {
//            String respMsg = (String) retBusiMap.get(CommonFieldName.RESP_MSG);
//            throw new BusinessException("接口编码code="+serverCode+",respCode="+respCode+",respMsg="+respMsg);
//        }
        return retBusiMap;
    }
    /**
     * 加密业务报文
     *
     * @param key
     * @param dataMap
     * @return
     */
    public static String encryptDataMap(String key, Map<String, Object> dataMap) throws Exception {
        String plainText = map2Plaintext(dataMap);
        return AESUtils.encryptWithBC(key, plainText);
    }
    /**
     * 参数按key顺序，然后转json
     *
     * @param dataMap
     * @return
     */
    public static String map2Plaintext(Map<String, Object> dataMap) {
        TreeMap<String, Object> orderedMap = new TreeMap<>(dataMap);
        return RequestClient.toJson(orderedMap);
    }

    /**
     * 读取私钥
     * @param prikeyFilePath
     * @param passwd
     * @param keyStoreType
     * @return
     * @throws Exception
     */
    public static PrivateKey getPriKey(String prikeyFilePath,String passwd,String keyStoreType ) throws Exception {
        //保存私钥pfx的文件路径
        String fileName = prikeyFilePath;
        InputStream inputStream = new FileInputStream(fileName);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int length = 0;
        while ((length = inputStream.read(buf)) != -1) {
            out.write(buf, 0, length);
        }
        byte[] result = out.toByteArray();
        byte[] fileContent = result;
        //私钥密码
        // String passwd = "123456";
        String pfxContent = Base64.getEncoder().encodeToString(fileContent);
        //私钥保存类型：PKCS12
        //PS:可以直接提取出私钥的pfx内容来保存，避免多次读取文件
        PrivateKey privateKey = readPrivateKey(pfxContent, passwd, keyStoreType);
        return privateKey;
    }

    public  Map<String, Object> entityToMap(Object object,String merchCd,String sequenceNo) {
        Map<String, Object> map = new HashMap();
        for (Field field : object.getClass().getDeclaredFields()){
            try {
                boolean flag = field.isAccessible();
                field.setAccessible(true);
                Object o = field.get(object);
                map.put(field.getName(), o);
                field.setAccessible(flag);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        map.put("merchCd", merchCd);
        map.put("sequenceNo", sequenceNo);
        map = dealBlank(map);
        return map;
    }
    public Map<String, Object> dealBlank(Map<String, Object> map) {
        //空键值对直接剔除
        Set set = map.keySet();
        for (Iterator iterator = set.iterator(); iterator.hasNext(); ) {
            Object obj = (Object) iterator.next();
            Object value = (Object) map.get(obj);
            remove(value, iterator);
        }
        return map;
    }
    private static void remove(Object obj, Iterator iterator) {
        if (obj instanceof String) {
            String str = (String) obj;
            if (str == null || str.trim().isEmpty()) {
                iterator.remove();
            }
        } else if (obj instanceof Collection) {
            Collection col = (Collection) obj;
            if (col == null || col.isEmpty()) {
                iterator.remove();
            }

        } else if (obj instanceof Map) {
            Map temp = (Map) obj;
            if (temp == null || temp.isEmpty()) {
                iterator.remove();
            }

        } else if (obj instanceof Object[]) {
            Object[] array = (Object[]) obj;
            if (array == null || array.length <= 0) {
                iterator.remove();
            }
        } else {
            if (obj == null) {
                iterator.remove();
            }
        }
    }
}


